#pragma once

#include "markup.h"
#include "mjson/json.h"
#include <gtl/io/file.h>
#include <gtl/container/array.h>

namespace gtl
{

namespace marks
{

class mjson
{
public:
	template<class Json>
	void parse(const tchar* buffer, Json* node)
	{
		json_t* jroot = NULL;
		json_error jerr = json_parse_document(&jroot, (char*)buffer);
		if(jerr != JSON_OK || jroot == NULL)
		{
			if(jroot != NULL)
				json_free_value(&jroot);

			return;
		}

		parse_element(jroot->child, node);
		json_free_value(&jroot);
	}

	template<class Json>
	void load(const gtl::tstr& file_path, Json* node)
	{
		gtl::file file(file_path, _T("rb"));
		if(file.size() == 0)
			return;

		size_t size = (size_t)file.size() + sizeof(gtl::wchar);
		gtl::array<char> data(size);
		file.read((char*)data, size);

		unsigned int code_page = CP_ACP;

		char* buff = data;
		if(static_cast<unsigned char>(buff[0]) == 0xEF && 
			static_cast<unsigned char>(buff[1]) == 0xBB && 
			static_cast<unsigned char>(buff[2]) == 0xBF)
		{
			// skip utf-8 bom
			parse(gtl::tstr_warp(buff + 3, CP_UTF8), node);
		}
		else if(static_cast<unsigned char>(buff[0]) == 0xFF && 
			static_cast<unsigned char>(buff[1]) == 0xFE)
		{
			// skip unicode bom
			parse(gtl::tstr_warp((gtl::wchar*)(buff + 2), code_page), node);
		}
		else
		{
			parse(gtl::tstr_warp(buff, code_page), node);
		}
	}

	template<class Json>
	bool save(const gtl::tstring& file_path, Json* node, const gtl::tchar* /*encoder*/)
	{
		gtl::file file;
		if(!file.open(file_path.c_str(), _T("wb")))
			return false;

		return = save(file, node);
	}

	template<class Json>
	gtl::tstr str(Json* node) const
	{
		gtl::tstr result;
		save(result, node);
		return result;
	}

private:
	void save(FILE*& fp, const gtl::tstr& value) const
	{
		if(value.empty())
			return;

		fwrite(value.c_str(), sizeof(gtl::tchar), value.length(), fp);
	}

	void save(gtl::tstr& result, const gtl::tstr& value) const
	{
		if(value.empty())
			return;

		result += value;
	}

	template<class Stream, class Json>
	bool save(Stream& strm, Json* node) const
	{
		if(node == NULL)
			return false;

		gtl::tstr space;
		for(int i = 0; i < node->depth(); ++i)
		{
			space += _T("\t");
		}

		if(node->tag().empty())
		{
			if(node->nodes().empty())
				save(strm, space + _T("{\n"));
			else
				save(strm, space + _T("[\n"));
		}
		else if(node->nodes().empty())
		{
			save(strm, space + _T("\"") + node->tag() + _T("\":\n") + space + _T("{\n"));
		}
		else
		{
			save(strm, space + _T("\"") + node->tag() + _T("\":\n") + space + _T("[\n"));
		}

		typename Json::attribs_type::const_iterator first = node->attribs().begin();
		typename Json::attribs_type::const_iterator last  = node->attribs().end();
		for(; first != last; ++first)
		{
			save(strm, space + _T("\t"));
			save(strm, _T("\"") + (*first).first + _T("\":\"") + (*first).second + _T("\""));
			if(std::distance(first, last) > 1)
				save(strm, gtl::tstr_warp(_T(",\n")));
			else if(node->nodes().empty())
				save(strm, gtl::tstr_warp(_T("\n")));
			else
				save(strm, gtl::tstr_warp(_T(",\n")));
		}

		typename Json::const_iterator begin = node->nodes().begin();
		typename Json::const_iterator end  = node->nodes().end();
		for(; begin != end; ++begin)
		{
			save(strm, *begin);
			if(std::distance(begin, end) > 1)
				save(strm, gtl::tstr_warp(_T(",\n")));
			else
				save(strm, gtl::tstr_warp(_T("\n")));
		}

		if(node->tag().empty())
		{
			if(node->nodes().empty())
				save(strm, space + _T("}"));
			else
				save(strm, space + _T("]"));
		}
		else
		{
			save(strm, space + _T("]"));
		}

		return true;
	}

private:
	template<class Json>
	void parse_element(json_t* jnode, Json* node, json_t* child_end = NULL, Json* parent = NULL)
	{
		if(jnode == NULL || node == NULL)
			return;

		Json* tmp_node = node;
		json_value_type jtype = jnode->type;
		if(jtype == JSON_OBJECT || jtype == JSON_ARRAY)
		{
			Json* prnt = parent == NULL ? node : parent;

			tmp_node = new Json;
			tmp_node->filters(prnt->filters());
			tmp_node->depth(prnt->depth() + 1);
			prnt->append(tmp_node);
			if(jnode->parent != NULL && jnode->parent->type == JSON_STRING && jnode->parent->text != NULL)
				tmp_node->tag(jnode->parent->text);

			if(jtype == JSON_ARRAY)
			{
				parent = tmp_node;
				child_end = jnode->child_end;
			}

			if(jnode == child_end)
				parent = NULL;
		}
		else if((jtype == JSON_STRING || jtype == JSON_NUMBER || jtype == JSON_TRUE || jtype == JSON_FALSE)
			&& jnode->parent != NULL && jnode->parent->type == JSON_STRING)
		{
			node->attribs().push_back(typename Json::attrib_type());
			typename Json::attrib_type& attrib = node->attribs().back();
			attrib.first = jnode->parent->text;
			attrib.second = jnode->text;
		}

		parse_element(jnode->child, tmp_node, child_end, parent);
		parse_element(jnode->next, tmp_node, child_end, parent);
	}
};

} // end of namespace marks

typedef markup<marks::mjson> json;

} // end of namespace gtl
